package filters;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.apache.log4j.Logger;

/**
 * <p>Recover session for cookie-less clients. It works by passing around 
 * the session ID in the request parameter JSESSIONID, and tries to recover the 
 * previous session if cookie is not available.</p>
 *
 * <p>It is recommended to pass the session ID with POST requests, to avoid
 * exposing the session ID in the URL. However, for file upload requests 
 * (enctype=multipart/form-data), the session ID must be given in the 
 * URL as it would otherwise be impossible to obtain the session ID without 
 * parsing the request content.</p>
 * 
 * <p>Limitations:</p>
 * <ul>
 * <li>The sessions recovered by request parameter do not have the last access
 * time updated. As a consequence such sessions tend to expire sooner than
 * those recovered normally from cookie by the container. The maximum inactive
 * interval for these sessions are therefore set to 3600 seconds as an interim
 * workaround, in the hope that most sessions should live no longer than this.</li>
 * <li>Since there is no way to prevent recovered sessions from being reaped
 * by the reaper, there will be race condition at the time when a session is 
 * just about to expire.</li>
 * </ul>
 * 
 * @author KENNETHLCH
 * @version $Id: CookielessSessionFilter.java,v 1.1.1.1 2013/08/16 05:35:48 tengyang Exp $
 */
public class CookielessSessionFilter implements Filter, HttpSessionListener {
    
    private static Logger logger = Logger.getLogger(CookielessSessionFilter.class);

    private static Map sessionCache = new HashMap();

    @Override
	public void destroy() {
        logger.info("destroy");
    }

    @Override
	public void init(FilterConfig config) throws ServletException {
        logger.info("init");
    }

    @Override
	public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        RequestWrapper wrapper = new RequestWrapper((HttpServletRequest) request);
        chain.doFilter(wrapper, response);
    }

    @Override
	public void sessionCreated(HttpSessionEvent event) {
        HttpSession session = event.getSession();
        
        synchronized (sessionCache) {
            sessionCache.put(session.getId(), session);
            logger.debug("created " + session.getId());
        }
    }

    @Override
	public void sessionDestroyed(HttpSessionEvent event) {
        HttpSession session = event.getSession();

        synchronized (sessionCache) {
            sessionCache.remove(session.getId());
            logger.debug("destroyed " + session.getId());
        }
    }

    class RequestWrapper extends HttpServletRequestWrapper {
        private String sid;

        public RequestWrapper(HttpServletRequest request) {
            super(request);

            if (getCookies() != null) // client supports cookies
                return;

            sid = request.getParameter("JSESSIONID");
            logger.debug("recovering " + sid);
        }

        @Override
		public HttpSession getSession() {
            return getSession(true);
        }

        @Override
		public HttpSession getSession(boolean create) {
            if (sid == null || !isRequestedSessionIdValid()) 
                return ((HttpServletRequest)getRequest()).getSession(create);
            
            HttpSession session = (HttpSession) sessionCache.get(sid);
            // The session may have just been invalidated by the reaper thread.
            if (session == null)
                return ((HttpServletRequest)getRequest()).getSession(create);
            
            // XXX workaround for the session timeout problem
            session.setMaxInactiveInterval(3600);
            
            // XXX even if we get here the session might still be invalidated by the reaper 
            // during the request cycle... 
            return session;
        }

        @Override
		public boolean isRequestedSessionIdValid() {
            if (sid == null)
                return ((HttpServletRequest)getRequest()).isRequestedSessionIdValid();
            
            return sessionCache.containsKey(sid);
        }

        public boolean isRequestedSessionIdFromParameter() {
            return sid != null;
        }

        
    }
}
